#include "parser.h"
#include "instructions.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

/**
 * Ajouter un élément à la liste chaînée
 *
 * @param tete		l'élément en tête de la liste auquel doit être raccordé le nouveau
 * @param pointeur	l'adresse mémoire de l'élément
 * @param nom		le nom du nouvel élément
 * @param longueur	la longueur de l'élément à ajouter
 *
 * @returns la nouvelle liste avec l'élément cible ajouté
 *
 */
Liste *add_List(Liste *dernier, REG pointeur, char *nom)
{
	// Allouer la liste que l'on va retourner
	//
	Liste *nouvelleListe = malloc(sizeof(Liste));

	// Allouer l'espace nécessaire pour le nom de l'élément
	//
	nouvelleListe->nom = malloc(sizeof(char)*(strlen(nom)+1));

	// Copier le nom de longueur prédéfinie dans la liste
	//
	strcpy(nouvelleListe->nom, nom);

	// Référencer l'adresse de l'élément cible
	nouvelleListe->adresse = pointeur;

	// Pointer vers le nouvel élément
	//
	nouvelleListe->suivant = dernier;

	return nouvelleListe;
}

/**
 * Se déplacer vers l'élément suivant
 *
 * @param actuel	la référence vers la liste cible
 *
 * @returns l'élément suivant de la liste
 *
 */
Liste *next_List(Liste *actuel)
{
	return actuel->suivant;
}

/**
 * Obtenir un élément de la liste
 *
 * @param dernier		pointeur sur la tête de liste
 * @param nom			le nom de l'élément
 * @param longueur		longueur du nom de l'élément
 * @param pointeur	 	pointeur de sortie de l'adresse de l'élément
 *
 * @returns true si l'élément a été trouvé et si le pointeur est écrit, faux sinon
 *
 */
bool get_List(Liste *dernier, char *nom, REG *pointeur)
{
	Liste* temp;

	// Parcourir la liste en allant d'élément en élément
	//
	for(temp = dernier; temp; temp = next_List(temp))
	{
		// Comparer le nom de l'élément selon sa longueur avec l'élément recherché
		//
		if(!strncmp(temp->nom, nom, strlen(nom)))
		{
			// Traiter le cas de l'élément trouvé : pointeur mis à jour
			//
			*pointeur = temp->adresse;
			return true;
		}
	}

	// Traiter le cas par défaut
	//
	return false;
}

/**
 * Convertit un caractère ASCII en une valeur alphanumérique dépendant de sa base
 *
 * @param ASCII		la valeur ASCII du nombre à convertir
 * @param base		la base de destination
 *
 * @returns la valeur du caractère correspondant à la base souhaitée
 */
char conversion_ASCII(char ASCII, int base)
{
	// Traiter les cas en fonction de chaque base
	//
	switch(base)
	{
	case 16:
		if((ASCII >= 'a' && ASCII <= 'f') || (ASCII >= 'A' && ASCII <= 'F'))
		{
			return tolower(ASCII) - 'a';
		}
		else
		{
			return ASCII - '0';
		}
		break;
	case 2:
		break;
	case 10:
		return ASCII - '0';
		break;
	default:
		return 0;
		break;
	}
	return 0;
}

/**
 * Parser un nombre
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param valeur		un pointeur vers un int
 * @param modifiable	assertion ou négation sur la modification s'une base
 *
 * @returns false si le nombre ne peut pas être lu, vrai sinon
 *
 */
bool parse_nombre(Parser* parser, int* valeur, bool modifiable)
{
	// Traiter le cas par défaut : nombre décimal
	//
	int base = 10;

	// Vérifier si le nombre n'est pas éntièrement un numérique
	//
	if(!isdigit(parser->current[parser->col]))
	{
		if(modifiable)
		{
			// Traiter le cas des nombres binaires
			//
			if(((parser->current[parser->col] == 'b') && (parser->current[parser->col+1] == '0'))
				|| (parser->current[parser->col+1] == '1'))
			{
				// Modifier la base courante
				//
				base = 2;

				// Se déplacer dans la ligne lue
				//
				parser->col++;
			}
		}
		// Traiter le cas par défaut
		//
		else
		{
			return false;
		}
	}

	// Traiter le cas d'un caractère héxa-décimal
	//
	if(parser->current[parser->col] == '0' && parser->current[parser->col+1] == 'x')
	{
		// Modifier la base
		//
		base = 16;
		parser->col += 2;
	}

	// Initialiser la valeur
	//
	*valeur = 0;

	// Lire le nombre
	//
	for(; parser->current[parser->col] && isdigit(parser->current[parser->col]); parser->col++)
	{
		// Convertir le nombre dans la base souhaitée
		//
		*valeur = *valeur * base + conversion_ASCII(parser->current[parser->col], base);
	}

	return valeur;
}

/**
 * Parser l'étiquette cible
 *
 * @param parser		le parser cible
 * @param adresse		l'adresse de l'étiquette
 *
 * @returns true si l'étiquette à été trouvée, false sinon
 */
bool parse_etiquette(Parser* parser, int* adresse) {

	char etiquette[256];

	//Recuperer l'etiquette
	//
	int i=0;
	for(i=0; !isblank(parser->current[parser->col]) && parser->current[parser->col] != '\0'
			 && parser->current[parser->col] != '\n'; parser->col++, i++) {

		etiquette[i] = parser->current[parser->col];
	}

	etiquette[parser->col] = '\0';
	REG reg;
	if(get_List(parser->labels, etiquette, &reg))
	{
		*adresse = reg;
		return true;
	}
	else
	{
		*adresse = 0;
		return false;
	}

}

/**
 * Parse un attribut
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param data			un pointeur vers les données (spécifiques aux modes direct/immédiat)
 * @param reg			un pointeur vers le numéro du registre
 * @param mod			le mode detecté
 *
 * @return false si l'instruction ne peut être lue, vrai sinon
 *
 */
bool parse_attribut(Parser *parser, int *data, int *reg, mode *mode)
{
	// Déclarer des booléens pour connaître le mode d'adressage
	//
	bool pointeur = false;
	bool registre = false;

	int nombre;

	// Passer les blancs
	//
	for(; isblank(parser->current[parser->col]); parser->col++)
	{}

	// Traiter le cas des modes direct/indirect spécifiés par des crochets
	//
	if(parser->current[parser->col] == '[')
	{
		pointeur = true;
		parser->col++;
	}

	// Traiter le cas du mode immediat, précédé par un dièse
	//
	if(parser->current[parser->col] == '#' && pointeur == false)
	{
		parser->col++;
	}

	// Traiter le cas d'un registre
	//
	if(isalpha(parser->current[parser->col]))
	{
		if((parser->current[parser->col] == 'R' || parser->current[parser->col] == 'r') &&
				isdigit(parser->current[parser->col+1]))
		{
			registre = true;
			parser->col++;
		}
	}

	//Verifier la présence d'un chiffre
	//
	if(isdigit(parser->current[parser->col]))
	{
		// Lire le nombre courant
		//
		if(!parse_nombre(parser, &nombre, !registre))
		{
			return false;
		}
	}
	//Traiter le cas d'une étiquette
	//
	else
	{
		//Si on trouve l'étiquette on recupère la valeur du REG dans nombre
		parse_etiquette(parser, &nombre);
	}

	// Trouver le mode d'adressage ciblé
	//
	if(registre)
	{
		*reg = nombre;
		if(pointeur)
		{
			*mode = INDIRECT;
		}
		else
		{
			*mode = REGISTER;
		}
	}
	else
	{
		*data = nombre;
		if(pointeur)
		{
			*mode = DIRECT;
		}
		else
		{
			*mode = IMMEDIATE;
		}
	}

	// Fermer les crochets
	//
	if(pointeur)
	{
		// Passer les blancs
		//
		for(; isblank(parser->current[parser->col]); parser->col++)
		{}

		// Vérifier la fermeture des crochets
		//
		if(parser->current[parser->col] != ']')
		{
			return false;
		}
		parser->col++;
	}
	return true;
}



/**
 * Parser des instructions ayant simplement une destination
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param mot			tableau contenant l'instruction et les valeurs
 * @param taille		la taille de l'instruction, et par la suite du tableau mot
 *
 * @returns faux en cas d'erreurs, vrai sinon
 */
bool parse_dest(Parser* parser, Mot mot[3], unsigned int *taille)
{
	// Intialiser les attributs conformes à la destination
	//
	int d_donnee, d_registre;
	mode mode;

	// Vérifier qu'il y a au moins un espace blanc avant l'instruction
	//
	if(!isblank(parser->current[parser->col]))
	{
		return false;
	}

	// Lire la destination cible
	//
	if(!parse_attribut(parser,&d_donnee, &d_registre, &mode))
	{
		return false;
	}

	// Traiter les cas selon le mode d'adressage courant
	//
	if(mode == REGISTER || mode == INDIRECT)
		mot[0].codage.dest = d_registre;

	if(mode == IMMEDIATE || mode == DIRECT)
		mot[(*taille)++].brut = d_donnee;

	// Trouver le mode d'adressage
	//
	switch(mode)
	{
	case REGISTER:
		mot[0].codage.mode = REGIMM;
		break;
	case INDIRECT:
	case DIRECT:
		mot[0].codage.mode = DIRREG;
		break;
	case IMMEDIATE:
		mot[0].codage.mode = REGISTER;
		break;
	}

	// Passer les blancs
	//
	for(; isblank(parser->current[parser->col]); parser->col++)
	{}

	return true;
}

/**
 * Parser des instructions ayant simplement une source
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param mot			tableau contenant l'instruction et les valeurs
 * @param taille		la taille de l'instruction, et par la suite du tableau mot
 *
 * @returns faux en cas d'erreurs, vrai sinon
 */
bool parse_source(Parser* parser, Mot mot[3], unsigned int *taille)
{
	// Intialiser les attributs conformes à la source
	//
	int s_donnee, s_registre;
	mode mode;

	// Vérifier qu'il y a au moins un espace blanc avant l'instruction
	//
	if(!isblank(parser->current[parser->col]))
	{
		return false;
	}

	// Lire la source cible
	//
	if(!parse_attribut(parser,&s_donnee, &s_registre, &mode))
	{
		return false;
	}

	// Traiter les cas selon le mode d'adressage courant
	//
	if(mode == REGISTER || mode == INDIRECT)
		mot[0].codage.source = s_registre;

	if(mode == IMMEDIATE || mode == DIRECT)
		mot[(*taille)++].brut = s_donnee;

	// Trouver le mode d'adressage
	//
	switch(mode)
	{
	case REGISTER:
		mot[0].codage.mode = REGREG;
		break;
	case INDIRECT:
		mot[0].codage.mode = REGIND;
		break;
	case DIRECT:
		mot[0].codage.mode = REGDIR;
		break;
	case IMMEDIATE:
		mot[0].codage.mode = REGIMM;
		break;
	}

	// Passer les blancs
	//
	for(; isblank(parser->current[parser->col]); parser->col++)
	{}

	return true;
}

/**
 * Parser des instructions ayant destination-source
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param mot			tableau contenant l'instruction et les valeurs
 * @param taille		la taille de l'instruction, et par la suite du tableau mot
 *
 * @returns faux en cas d'erreurs, vrai sinon
 */
bool parse_destsource(Parser* parser, Mot mot[3], unsigned int *taille)
{
	// Intialiser les attributs conformes à la source
	//
	int s_donnee, s_registre;
	mode s_mode;

	// Intialiser les attributs conformes à la destination
	//
	int d_donnee, d_registre;
	mode d_mode;

	// Vérifier qu'il y a au moins un espace blanc avant l'instruction
	//
	if(!isblank(parser->current[parser->col]))
	{
		return false;
	}

	// Lire la destination cible
	//
	if(!parse_attribut(parser,&d_donnee, &d_registre, &d_mode))
	{
		return false;
	}

	// Traiter les cas selon le mode d'adressage courant
	//
	if(d_mode == REGISTER || d_mode == INDIRECT)
		mot[0].codage.dest = d_registre;

	if(d_mode == IMMEDIATE || d_mode == DIRECT)
		mot[(*taille)++].brut = d_donnee;

	// Vérifier la virgule entre source et destination
	//
	if (parser->current[parser->col] == ',')
	{
		parser->col++;
	}
	else
	{
		return false;
	}

	// Passer les blancs
	//
	for(; isblank(parser->current[parser->col]); parser->col++)
	{}

	// Lire la source cible
	//
	if(!parse_attribut(parser,&s_donnee, &s_registre, &s_mode))
	{
		return false;
	}

	// Traiter les cas selon le mode d'adressage courant
	//
	if(s_mode == REGISTER || s_mode == INDIRECT)
		mot[0].codage.source = s_registre;

	if(s_mode == IMMEDIATE || s_mode == DIRECT)
		mot[(*taille)++].brut = s_donnee;

	// Passer les blancs
	//
	for(; isblank(parser->current[parser->col]); parser->col++)
	{}

	// Trouver le mode d'adressage
	//
	switch(d_mode)
	{
	case REGISTER:
		switch(s_mode)
		{
		case REGISTER :	mot[0].codage.mode = REGREG; break;
		case INDIRECT :	mot[0].codage.mode = REGIND; break;
		case DIRECT   :	mot[0].codage.mode = REGDIR; break;
		case IMMEDIATE:	mot[0].codage.mode = REGIMM; break;
		}
		break;
		case INDIRECT:
			switch(s_mode)
			{
			case REGISTER :	mot[0].codage.mode = INDREG; break;
			case IMMEDIATE:	mot[0].codage.mode = INDIMM; break;
			default       :	mot[0].codage.mode = REGISTER; break;
			}
			break;
			case DIRECT:
				switch(s_mode)
				{
				case REGISTER :	mot[0].codage.mode = DIRREG; break;
				case IMMEDIATE:	mot[0].codage.mode = DIRIMM; break;
				default       :	mot[0].codage.mode = REGISTER; break;
				}
				break;
				case IMMEDIATE:
					mot[0].codage.mode = REGISTER;
					break;
	}

	return true;
}

/**
 * Parser une instruction unique en connaissant son code-operande
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param mot			tableau contenant l'instruction et les valeurs
 * @param taille		la taille de l'instruction, et par la suite du tableau mot
 *
 * @returns faux en cas d'erreurs, vrai sinon
 */
bool parse_instruction(Parser* parser, Mot mot[3], unsigned int *taille)
{
	// Récupérer l'instruction cible
	//
	Instruction instruction = getInstruction(mot[0]);

	// Traiter le cas de la source
	//
	if (instruction.source)
	{
		// Traiter le cas d'un source-destination
		//
		if (instruction.destination)
		{
			return parse_destsource(parser, mot, taille);
		}
		// Traiter le cas d'une unique source
		//
		else
		{
			return parse_source(parser, mot, taille);
		}
	}
	// Traiter le cas de la destination
	//
	else
	{
		// Traiter le cas d'une unique destination
		//
		if (instruction.destination)
		{
			return parse_dest(parser, mot, taille);
		}
	}
	return true;
}

/**
 * Parser une ligne de code
 *
 * @param parser		un pointeur vers la structure du pointeur
 * @param ligne			pointeur vers la ligne cible
 *
 * @return false en cas d'erreur, true sinon
 *
 */
bool parse_ligne(Parser* parser, char *ligne)
{
	char instruction[256];
	Mot m[3];
	unsigned int taille;

	parser->current = ligne;
	parser->col = 0;

	// Parcourir l'instruction
	//
	for(; !isblank(parser->current[parser->col]) && parser->current[parser->col] != '\0'
		  && parser->current[parser->col] != '\n'; parser->col++) {
		//Recopier l'instruction
		//
		instruction[parser->col] = parser->current[parser->col];
	}

	//Terminer la chaine
	//
	instruction[parser->col] = '\0';

	//Lire l'instruction
	//
	int len = strlen(instruction);
	if (instruction[len - 1] == ':')
	{
		return true;
	}

	taille = 1;
	//parser->current += len;
	//parser->col += len;

	if(!strcasecmp(instruction, "load"))
		m[0].codage.codeop = LOAD;

	else if(!strcasecmp(instruction, "store"))
		m[0].codage.codeop = STORE;

	else if(!strcasecmp(instruction, "add"))
		m[0].codage.codeop = ADD;

	else if(!strcasecmp(instruction, "sub"))
		m[0].codage.codeop = SUB;

	else if(!strcasecmp(instruction, "and"))
		m[0].codage.codeop = AND;

	else if(!strcasecmp(instruction, "or"))
		m[0].codage.codeop = OR;

	else if(!strcasecmp(instruction, "shl"))
		m[0].codage.codeop = SHL;

	else if(!strcasecmp(instruction, "shr"))
		m[0].codage.codeop = SHR;

	else if(!strcasecmp(instruction, "jmp"))
		m[0].codage.codeop = JMP;

	else if(!strcasecmp(instruction, "jeq"))
		m[0].codage.codeop = JEQ;

	else if(!strcasecmp(instruction, "call"))
		m[0].codage.codeop = CALL;

	else if(!strcasecmp(instruction, "ret"))
		m[0].codage.codeop = RET;

	else if(!strcasecmp(instruction, "push"))
		m[0].codage.codeop = PUSH;

	else if(!strcasecmp(instruction, "pop"))
		m[0].codage.codeop = POP;

	else if(!strcasecmp(instruction, "mov"))
		m[0].codage.codeop = MOV;

	else if(!strcasecmp(instruction, "halt"))
		m[0].codage.codeop = HALT;

	else
	{
		if (instruction[0] == ';' || instruction[0] == '#')
		{
			return true;
		}

		printf("Instruction inconnue : `%s'\n", instruction);
	}

	if(!parse_instruction(parser, m, &taille))
		return false;


	// write instruction into the memory
	if(taille > 0)
	{

		int i;
		for(i=0; i < taille; i++)
		{
			parser->mem[parser->pc+i].brut = m[i].brut;
		}

		parser->pc += taille;
	}

	return true;
}

/**
 * Récupérer les etiquettes du code assembleur cible
 *
 * @param parser		le parser cible
 *
 */
bool recup_etiquette(Parser* parser)
{
	char line[100];


	parser->pc = 0;

	// read the buffer line by line
	while(fgets(line, sizeof(line), parser->fp) != NULL)
	{
		char instruction[256];

		parser->row++;
		parser->current = line;
		parser->col = 0;

		//Lire l'instruction
		//
		for(; !isblank(parser->current[parser->col]) && parser->current[parser->col] != '\0'
			  && parser->current[parser->col] != '\n'; parser->col++) {
			//Recopier l'instruction
			//
			instruction[parser->col] = parser->current[parser->col];
		}

		instruction[parser->col] = '\0';

		int len = strlen(instruction);
		if (instruction[len - 1] == ':')
		{
			instruction[len-1] = '\0';
			parser->labels = add_List(parser->labels, parser->pc, instruction);
		}

		if(!parse_ligne(parser, line))
		{
			return false;
		}
	}


	return true;
}

/**
 * Créer un nouveau parseur
 *
 * @param f 	le fichier cible
 *
 */
Parser* newParser(FILE *f) {

	Parser* newParser = malloc(sizeof(Parser));
	newParser->col=0;
	newParser->fp =f;
	newParser->mem = malloc(sizeof(Mot)*MEMSIZE);
	newParser->labels = malloc(sizeof(Liste));
	newParser->labels->nom = "Init";

	return newParser;
}

/**
 * Charger le programme cible
 *
 * @param filename  le nom du fichier cible
 * @param pvm       la pvm cible
 *
 */
bool load_program(char *filename, PVM* pvm)
{

	FILE *f = fopen(filename, "r");

	if(f != NULL) {
		printf("Ouverture du fichier réussi !\n");
		// Creer le parseur
		//
		Parser* parserTmp = newParser(f);
		recup_etiquette(parserTmp);
		rewind(f);
		Parser* parser = newParser(f);
		parser->labels = parserTmp->labels;

		// Lire et parser chaque ligne du fichier
		//
		char ligne[100];

		while(!feof(f)) {
			fgets(ligne, 100,f);
			printf("%s", ligne);
			parse_ligne(parser, ligne);
		}

		pvm->mem = parser->mem;

		fclose(f);
		return true;

	}
	else {
		printf("Imposible d'ouvrir le fichier : %s\n", filename);
		return false;
	}

}


